Here, we will explore the use of LIMMA (“linear models for microarray data”) for performing linear modelling.
The original limma publication (2004!!) is here: https://www.ncbi.nlm.nih.gov/pubmed/16646809 For a proper explanation of the statitical model see: https://www.ncbi.nlm.nih.gov/pmc/articles/PMC5373812/
To save repeating the work of other in describing the use of limma, I refer you to this introduction from Kasper D. Hansen: https://kasperdanielhansen.github.io/genbioconductor/html/limma.html.
Note that the data used in the above is microarray data in an ExpressionSet object. However, limma is agnostic to the type of input data and is perfectly suitable for proteomics data so long as it’s reasonable to assume the quantification values are approximately gaussian distributed. For this reason, the quantification values should first be log transformed.
As stated in the documentation for the MSnSet class (https://www.rdocumentation.org/packages/MSnbase/versions/1.20.7/topics/MSnSet-class): " The MSnSet class is derived from the eSet class and mimics the ExpressionSet class classically used for microarray data." It’s therefore relatively straightforward to use limma with proteomics data in a MSnSet.
# load packages
library(tidyverse)
library(limma)
library(biobroom)
library(Hmisc)
library(MSnbase)
# set up standardised plotting scheme
theme_set(theme_bw(base_size = 20) +
theme(panel.grid.major=element_blank(),
panel.grid.minor=element_blank(),
aspect.ratio=1))
cbPalette <- c("#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7", "#999999")
Again, we read in the MSnSets and subset to the samples of interest
total_protein_quant <- readRDS("../raw/total_res_pro_agg_norm.rds")
rbp_protein_quant <- readRDS("../raw/rbp_res_pro_agg_norm.rds")
# identify the samples we want to keep
samples_to_keep <- grep("M_|G1_", pData(total_protein_quant)$Sample_name)
print(samples_to_keep)
[1] 2 3 4 5 6 7
total_protein_quant <- total_protein_quant[,samples_to_keep]
Let’s start by applying limma to the total protein quantification data only. First of all we need to create a design matrix. We can do this from the pData since this contains the information about the samples
rbp_protein_quant <- rbp_protein_quant[,samples_to_keep]
condition <- pData(total_protein_quant)$Condition
design <- model.matrix(~condition)
print(design)
(Intercept) conditionM
1 1 1
2 1 1
3 1 1
4 1 0
5 1 0
6 1 0
attr(,"assign")
[1] 0 1
attr(,"contrasts")
attr(,"contrasts")$condition
[1] "contr.treatment"
Then we fit the model using this design and update the estimates for the standard errors for each coefficient using the eBayes function. As expected, there is a relationship between mean intensity and variance, although this is almost all limited to the very low intensity values.
Limma will use this relationship to moderate the standard errors for the coefficients estimated such that the per-protein variance estimates are “squeezed” towards the expectation derived from other proteins with similar mean intensity.
tidy(total_protein_quant, addPheno=TRUE) %>%# "tidy" the object, e.g make it into a tidy data format --> long
group_by(protein, Condition) %>% # group by protein and condition
dplyr::summarise(mean=mean(value), sqrt_sd=sqrt(sd(value))) %>% # mean and var for each group
ggplot(aes(mean, sqrt_sd)) + # plot mean(intensity) vs sqrt(sd(intensity))
geom_point(size=0.1) +
geom_smooth(se=FALSE, method="loess") + # local regression
xlab("Mean intensity") +
ylab("sqrt(sd/mean)")

Below we run limma to idenify the proteins with a significant change in abundance between conditions
total_fit_lm <- lmFit(exprs(total_protein_quant), design) # fit model to each protein
total_fit_lm_c <- contrasts.fit(total_fit_lm, coefficients=c("conditionM")) # extract results for coefficient of interest
total_fit_lm_e_c <- eBayes(total_fit_lm_c) # shrink std errors to abundance vs. stdev trend
p_value_status <- ifelse(total_fit_lm_e_c$p.value[,'conditionM']<0.01, "sig", "not_sig") # identify significant changes
# plot
limma::plotMA(total_fit_lm_e_c, status=p_value_status,
col=c(cbPalette[6], "black"), cex=c(0.5,0.1), main="")

Note that most of these changes are relatively slight (<2-fold)
# Extract all results from limma (n=Inf)
all_results <- topTable(total_fit_lm_e_c, coef = "conditionM", n = Inf)
my_volcanoplot <- function(topTableResults){
p <- topTableResults %>%
mutate(sig=ifelse(adj.P.Val<0.01, "sig.", "not sig.")) %>% # add "sig" column
ggplot(aes(logFC, -log10(P.Value), colour=sig)) +
geom_point(size=0.25) +
scale_colour_manual(values=c("black", cbPalette[6]), name="") # manually adjust colours
return(p)
}
my_volcanoplot(all_results)

OK, so it’s easy to perform the pairwise comparison. What about changes in RNA binding? For this, we need combine the two MSnSets into a single ExpressionSet
intersecting_proteins <- intersect(rownames(total_protein_quant), rownames(rbp_protein_quant))
total_for_combination <- total_protein_quant[intersecting_proteins,]
rbp_for_combination <- rbp_protein_quant[intersecting_proteins,]
# make the column names for the two MSnSets unique
colnames(total_for_combination) <- paste0(colnames(total_for_combination), "_Total")
colnames(rbp_for_combination) <- paste0(colnames(rbp_for_combination), "_OOPS")
# make the ExpressionSet
combined_intensities <- ExpressionSet(cbind(exprs(total_for_combination), exprs(rbp_for_combination)))
# Add the feature data
fData(combined_intensities) <- fData(total_for_combination)
# Add the phenotype data
pData(combined_intensities) <- rbind(pData(total_for_combination), pData(rbp_for_combination))
pData(combined_intensities)$Condition <- factor(pData(combined_intensities)$Condition, level=c("M","G1"))
pData(combined_intensities)$Type <- factor(pData(combined_intensities)$Type, level=c("Total","OOPS"))
dim(exprs(combined_intensities))
[1] 1916 12
print(head(data.frame(exprs(combined_intensities)), 2))
print(head(fData(combined_intensities), 2))
print(head(pData(combined_intensities), 2))
condition <- combined_intensities$Condition
type <- combined_intensities$Type
sample_name <- combined_intensities$Sample_name
design <- model.matrix(~condition*type)
rna_binding_fit <- lmFit(combined_intensities, design)
rna_binding_fit <- contrasts.fit(rna_binding_fit, coefficients="conditionG1:typeOOPS")
rna_binding_fit <- eBayes(rna_binding_fit)
rna_binding_p_value_status <- ifelse(rna_binding_fit$p.value[,'conditionG1:typeOOPS']<0.01, "sig", "not_sig")
limma::plotMA(rna_binding_fit, status=rna_binding_p_value_status, values=c("sig", "not_sig"),
col=c(cbPalette[6], "black"), cex=c(0.5,0.1), main="")

Below, we summarise the number of signficant p-values (post BH FDR correction) using a 1% FDR threshold.
summary(decideTests(rna_binding_fit, p.value=0.01, adjust.method="BH"))
conditionG1:typeOOPS
Down 432
NotSig 1047
Up 437
And then make another volcano plot
all_rna_binding_results <- topTable(rna_binding_fit, coef = "conditionG1:typeOOPS", n = Inf, confint=TRUE)
my_volcanoplot(all_rna_binding_results)

So again, lots of RNA binding changes!
Now, let’s compare the results from the two methods. To do this, we will merge together the results from the two methods.
M_G1_simple_lm <- readRDS("../results/M_G1_changes_in_RNA_binding_linear_model.rds")
compare_methods <- all_rna_binding_results %>%
dplyr::select("logFC", "AveExpr", "adj.P.Val", "P.Value") %>%
merge(M_G1_simple_lm, by.x="row.names", by.y="protein")
First, let’s tabulate the proteins significant in each method
lm_sig <- ifelse(compare_methods$lm_BH<0.01, "lm sig", "lm not sig")
limma_sig <- ifelse(compare_methods$adj.P.Val<0.01, "limma sig", "limma not sig")
print(table(lm_sig, limma_sig))
limma_sig
lm_sig limma not sig limma sig
lm not sig 1022 201
lm sig 25 668
compare_methods$sig_status <- interaction(lm_sig, limma_sig)
OK, so most proteins with significant change in RNA binding using lm or limma are significant in both, although limma does indicate more proteins have a significant change. Note that only 25/1916 proteins are significant by lm only.
What about if we separate by the lm model used, e.g +/- tag
fit <- compare_methods$fit
print(table(lm_sig, limma_sig, fit))
, , fit = With_tag
limma_sig
lm_sig limma not sig limma sig
lm not sig 541 139
lm sig 18 349
, , fit = Without_tag
limma_sig
lm_sig limma not sig limma sig
lm not sig 481 62
lm sig 7 319
OK, so lm and limma results are more similar if the lm model did not include the tag. This is no suprise since the limma fomula we used did not include the tag covariate so this is the closest comparison
First, let’s compare the p-values. Note that the p-values are usually lower in limma. The second plot shows the threshold for the maximum p-value which results in an estimated FDR < 1% for both methods.
max_p_sig_lm <- compare_methods %>% filter(lm_BH<0.01) %>% pull(lm_p_value) %>% max()
max_p_sig_limma <- compare_methods %>% filter(adj.P.Val<0.01) %>% pull(P.Value) %>% max()
p <- compare_methods %>%
ggplot(aes(log10(lm_p_value), log10(P.Value))) +
geom_point() +
geom_abline(slope=1, linetype=2, colour=cbPalette[6]) +
xlab("lm") +
ylab("limma")
print(p)

print(p +
geom_vline(xintercept=log10(max_p_sig_lm), linetype=2) +
geom_hline(yintercept=log10(max_p_sig_limma), linetype=2))

What about if we separate the proteins by their intensity?
compare_methods %>%
mutate(binned_ave_exprs=cut2(AveExpr, g=10)) %>% # bin the AveExpr using Hmisc::cut()
ggplot(aes(log10(lm_p_value), log10(P.Value))) +
geom_point() +
geom_abline(slope=1, linetype=2) +
xlab("lm") +
ylab("limma") +
facet_wrap(~binned_ave_exprs)

Note that the estimates for the fold change are not changed by the bayesian shrinkage of the coefficient standard errors.
compare_methods %>% ggplot(aes(log10(lm_fold_change), log10(logFC))) +
geom_point(size=1, alpha=0.2) +
xlab("lm") +
ylab("limma") +
ggtitle("Estimated fold changes")

Finally, let’s explore some of the proteins which were detected as having a significant change in RNA binding with only one method. Remember from above that the p-values for lm and limma are very well correlated so we’re looking here at slight differences close to the 1% FDR thresholds.
First, we need a function to plot the intensities for a protein(s)
# Function to plot the intensities values
plotIntensities <- function(obj){
p <- tidy(obj, addPheno=TRUE) %>%
ggplot(aes(Condition, value, colour=Type, group=Type)) +
geom_point() +
stat_summary(geom="line", fun.y=mean) +
facet_wrap(~gene, scales='free') +
ylab("Intensity (log2)")
invisible(p)
}
Let’s look at the proteins which are “lm only”. We’ll ignore those where we used the TMT tag in our lm model since this was not included in the limma model so that may be another reason for differences in the p-values.
lm_only <- compare_methods %>%
filter(fit=="Without_tag") %>% # Restrict to protein where lm model did not include the tag
filter(sig_status=='lm sig.limma not sig') %>% # sig in lm only
dplyr::select(Row.names, lm_fold_change, P.Value, adj.P.Val, lm_p_value, lm_BH, lm_std_error) %>% # select columns
arrange(desc(P.Value)) # arrange by limma p-value (descending order)
print(lm_only)
Now let’s plot these proteins. Notice that in all cases, the replicates are very tightly distributed.
print(plotIntensities(combined_intensities[lm_only$Row.names,]))

In some cases, the intensity values are near identical (see below).
# Heterogeneous nuclear ribonucleoprotein U-like protein 1
# HNRNPUL1
# Acts as a basic transcriptional regulator. Represses basic transcription driven by several virus and cellular promoters.
# When associated with BRD7, activates transcription of glucocorticoid-responsive promoter in the absence of
# ligand-stimulation. Plays also a role in mRNA processing and transport. Binds avidly to poly(G) and poly(C) RNA
# homopolymers in vitro.
tidy(combined_intensities, addPheno=TRUE) %>%
filter(gene=="Q9BUJ2", Type=="OOPS", Condition=="M") %>%
dplyr::select(Condition, Replicate, value)
Below, we explore the observed intensity values for some of the proteins which are significant according only to limma. Note that these have relatively large variability in comparison.
limma_only <- compare_methods %>%
filter(sig_status=='lm not sig.limma sig', fit=="Without_tag") %>%
dplyr::select(Row.names, lm_fold_change, P.Value, adj.P.Val, lm_p_value, lm_BH, lm_std_error) %>%
arrange(desc(lm_p_value))
print(head(limma_only))
print(plotIntensities(combined_intensities[limma_only$Row.names[1:9],]))

Let’s go back to that plot of mean vs sqrt and see how the proteins compare depending on whether they were detected as signficant in each method.
As expected, those significant in just lm have relatively low observed std. dev. and those significant in just limma have relatively high std. dev.
mean_sd_data <- tidy(total_protein_quant, addPheno=TRUE) %>%# "tidy" the object, e.g make it into a tidy data format --> long
group_by(protein, Condition) %>% # group by protein and condition
dplyr::summarise(mean=mean(value), sqrt_sd=sqrt(sd(value))) %>% # mean and stdev for each group
ungroup() %>%
merge(compare_methods, by.x="protein", by.y="Row.names") %>% # merge in the results from the two methods
filter(fit=="Without_tag") # Only keep those proteins fitted without the tag covariate in lm
# remake the basic plot showing the relationship
p_basic <- mean_sd_data %>%
ggplot(aes(mean, sqrt_sd)) +
xlab("Mean intensity") +
ylab("Coefficient of variance\n(sd/mean)") +
xlim(0, NA) + ylim(0, NA) # include 0,0
print(p_basic + geom_point(size=0.2, alpha=0.5) + geom_smooth(se=FALSE))

p_density <- p_basic +
geom_density_2d(aes(colour=sig_status), alpha=0.5, size=0.5) + # density of points
scale_colour_manual(values=cbPalette[c(2:4,6)])# set colours
print(p_density)

Finally, we can get limma to return the signficant changes using both p-value and log-fold change thresholds using the TREAT method (https://www.ncbi.nlm.nih.gov/pubmed/19176553). Note that this is not the same as thresholding on the p-value and the point estimate for the fold change as limma is actually testing the null hypothesis that the fold change is less than our specified threshold. To be explicit, let’s check the difference
sig_changes_p <- topTable(rna_binding_fit, coef = "conditionG1:typeOOPS", n = Inf, p.value=0.01, adjust.method="fdr", confint=0.95)
sig_changes_p_logfc_point_estimate <- sig_changes_p[abs(sig_changes_p$logFC)>1,]
cat(sprintf("%s proteins pass adjusted p-value threshold, of which %s pass the threshold on 2 fold change point estimate\n",
nrow(sig_changes_p), nrow(sig_changes_p_logfc_point_estimate)))
869 proteins pass adjusted p-value threshold, of which 202 pass the threshold on 2 fold change point estimate
rna_binding_fit_treat <- treat(rna_binding_fit,lfc=1) # Test null hypothesis than change is <2-fold
sig_changes_p_logfc <- topTreat(rna_binding_fit_treat, coef = "conditionG1:typeOOPS", n = Inf,
p.value=0.01, lfc=1, adjust.method="fdr", confint=0.95)
cat(sprintf("%s proteins pass the combined adjusted p-value threshold + fold change > 2\n", nrow(sig_changes_p_logfc)))
8 proteins pass the combined adjusted p-value threshold + fold change > 2
And below we reproduce our volcano plot including the 95% confidence interval and highlight those proteins which have < 1% FDR and an absolute fold change significantly greater than 2. Below, we can see that many of the proteins with a fold change (FC) point estimate > 2 have a 95% confidence interval that overlaps the dashed lines for >2-fold change. TREAT also takes the multiple testing into account so it’s even more conservative than just using the 95% CI shown below.
Of course, the threshold for the fold changes you are interested in depends entirely on your prior expectations.
.tmp_df <- all_rna_binding_results
.tmp_df$sig <- ifelse(.tmp_df$P.Value<=0.01, "<1% FDR", ">1% FDR") # add "sig" column
.tmp_df$sig[rownames(.tmp_df) %in% rownames(sig_changes_p_logfc_point_estimate)] <- "<1% FDR. FC point estimate < 2"
.tmp_df$sig[rownames(.tmp_df) %in% rownames(sig_changes_p_logfc)] <- "<1% FDR. TREAT FC < 2"
.tmp_df$SE <- sqrt(rna_binding_fit$s2.post) * rna_binding_fit$stdev.unscaled[,1]
p <- .tmp_df %>%
ggplot(aes(logFC, -log10(P.Value), colour=sig)) +
geom_point(size=1) +
scale_colour_manual(values=c(cbPalette[c(6,2,7)], "grey20"), name="") + # manually adjust colours
geom_vline(xintercept=1, linetype=2, colour="grey70") +
geom_vline(xintercept=-1, linetype=2, colour="grey70") +
theme(legend.position="top", legend.direction=2)
print(p)

print(p + geom_errorbarh(aes(xmin=CI.L, xmax=CI.R)))

Finally, let’s save out the results objects for later notebooks.
saveRDS(rna_binding_fit, "../results/limma_rna_binding_fit.rds")
saveRDS(all_rna_binding_results, "../results/limma_rna_binding_results.rds")
saveRDS(rna_binding_fit_treat, "../results/limma_rna_binding_results_treat.rds")
saveRDS(compare_methods, "../results/compare_methods_rna_binding_results.rds")
saveRDS(combined_intensities, "../results/combined_intensities.rds")
LS0tCnRpdGxlOiAiVXNpbmcgTElNTUEgaW4gcHJvdGVvbWljcyIKaGVhZGVyLWluY2x1ZGVzOgotIFx1c2VwYWNrYWdle3hjb2xvcn0KLSBcdXNlcGFja2FnZXtmcmFtZWR9Cm91dHB1dDoKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKICBodG1sX25vdGVib29rOiBkZWZhdWx0Ci0tLQpcY29sb3JsZXR7c2hhZGVjb2xvcn17bGlnaHRncmF5ITEwfSAKCkhlcmUsIHdlIHdpbGwgZXhwbG9yZSB0aGUgdXNlIG9mIExJTU1BICjigJxsaW5lYXIgbW9kZWxzIGZvciBtaWNyb2FycmF5IGRhdGHigJ0pIGZvciBwZXJmb3JtaW5nIGxpbmVhciBtb2RlbGxpbmcuCgpUaGUgb3JpZ2luYWwgbGltbWEgcHVibGljYXRpb24gKDIwMDQhISkgaXMgaGVyZTogaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wdWJtZWQvMTY2NDY4MDkKRm9yIGEgcHJvcGVyIGV4cGxhbmF0aW9uIG9mIHRoZSBzdGF0aXRpY2FsIG1vZGVsIHNlZTogaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNTM3MzgxMi8KClRvIHNhdmUgcmVwZWF0aW5nIHRoZSB3b3JrIG9mIG90aGVyIGluIGRlc2NyaWJpbmcgdGhlIHVzZSBvZiBgbGltbWFgLCBJIHJlZmVyIHlvdSB0byB0aGlzIGludHJvZHVjdGlvbiBmcm9tIEthc3BlciBELiBIYW5zZW46Cmh0dHBzOi8va2FzcGVyZGFuaWVsaGFuc2VuLmdpdGh1Yi5pby9nZW5iaW9jb25kdWN0b3IvaHRtbC9saW1tYS5odG1sLiAKCk5vdGUgdGhhdCB0aGUgZGF0YSB1c2VkIGluIHRoZSBhYm92ZSBpcyBtaWNyb2FycmF5IGRhdGEgaW4gYW4gYEV4cHJlc3Npb25TZXRgIG9iamVjdC4gSG93ZXZlciwgbGltbWEgaXMgYWdub3N0aWMgdG8gdGhlIHR5cGUgb2YgaW5wdXQgZGF0YSBhbmQgaXMgcGVyZmVjdGx5IHN1aXRhYmxlIGZvciBwcm90ZW9taWNzIGRhdGEgc28gbG9uZyBhcyBpdCdzIHJlYXNvbmFibGUgdG8gYXNzdW1lIHRoZSBxdWFudGlmaWNhdGlvbiB2YWx1ZXMgYXJlIGFwcHJveGltYXRlbHkgZ2F1c3NpYW4gZGlzdHJpYnV0ZWQuIEZvciB0aGlzIHJlYXNvbiwgdGhlIHF1YW50aWZpY2F0aW9uIHZhbHVlcyBzaG91bGQgZmlyc3QgYmUgbG9nIHRyYW5zZm9ybWVkLiAKCkFzIHN0YXRlZCBpbiB0aGUgZG9jdW1lbnRhdGlvbiBmb3IgdGhlIE1TblNldCBjbGFzcyAoaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL01TbmJhc2UvdmVyc2lvbnMvMS4yMC43L3RvcGljcy9NU25TZXQtY2xhc3MpOiAiIFRoZSBgTVNuU2V0YCBjbGFzcyBpcyBkZXJpdmVkIGZyb20gdGhlIGBlU2V0YCBjbGFzcyBhbmQgbWltaWNzIHRoZSBgRXhwcmVzc2lvblNldGAgY2xhc3MgY2xhc3NpY2FsbHkgdXNlZCBmb3IgbWljcm9hcnJheSBkYXRhLiIgSXQncyB0aGVyZWZvcmUgcmVsYXRpdmVseSBzdHJhaWdodGZvcndhcmQgdG8gdXNlIGBsaW1tYWAgd2l0aCBwcm90ZW9taWNzIGRhdGEgaW4gYSBgTVNuU2V0YC4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIGxvYWQgcGFja2FnZXMKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkobGltbWEpCmxpYnJhcnkoYmlvYnJvb20pCmxpYnJhcnkoSG1pc2MpCmxpYnJhcnkoTVNuYmFzZSkKCiMgc2V0IHVwIHN0YW5kYXJkaXNlZCBwbG90dGluZyBzY2hlbWUKdGhlbWVfc2V0KHRoZW1lX2J3KGJhc2Vfc2l6ZSA9IDIwKSArCiAgICAgICAgICAgIHRoZW1lKHBhbmVsLmdyaWQubWFqb3I9ZWxlbWVudF9ibGFuaygpLAogICAgICAgICAgICAgICAgICBwYW5lbC5ncmlkLm1pbm9yPWVsZW1lbnRfYmxhbmsoKSwKICAgICAgICAgICAgICAgICAgYXNwZWN0LnJhdGlvPTEpKQoKY2JQYWxldHRlIDwtIGMoIiNFNjlGMDAiLCAiIzU2QjRFOSIsICIjMDA5RTczIiwgIiNGMEU0NDIiLCAiIzAwNzJCMiIsICIjRDU1RTAwIiwgIiNDQzc5QTciLCAiIzk5OTk5OSIpCmBgYAoKQWdhaW4sIHdlIHJlYWQgaW4gdGhlIE1TblNldHMgYW5kIHN1YnNldCB0byB0aGUgc2FtcGxlcyBvZiBpbnRlcmVzdApgYGB7cn0KdG90YWxfcHJvdGVpbl9xdWFudCA8LSByZWFkUkRTKCIuLi9yYXcvdG90YWxfcmVzX3Byb19hZ2dfbm9ybS5yZHMiKQpyYnBfcHJvdGVpbl9xdWFudCA8LSByZWFkUkRTKCIuLi9yYXcvcmJwX3Jlc19wcm9fYWdnX25vcm0ucmRzIikKCiMgaWRlbnRpZnkgdGhlIHNhbXBsZXMgd2Ugd2FudCB0byBrZWVwCnNhbXBsZXNfdG9fa2VlcCA8LSBncmVwKCJNX3xHMV8iLCBwRGF0YSh0b3RhbF9wcm90ZWluX3F1YW50KSRTYW1wbGVfbmFtZSkKcHJpbnQoc2FtcGxlc190b19rZWVwKQoKdG90YWxfcHJvdGVpbl9xdWFudCA8LSB0b3RhbF9wcm90ZWluX3F1YW50WyxzYW1wbGVzX3RvX2tlZXBdCnJicF9wcm90ZWluX3F1YW50IDwtIHJicF9wcm90ZWluX3F1YW50WyxzYW1wbGVzX3RvX2tlZXBdCmBgYAoKTGV0J3Mgc3RhcnQgYnkgYXBwbHlpbmcgbGltbWEgdG8gdGhlIHRvdGFsIHByb3RlaW4gcXVhbnRpZmljYXRpb24gZGF0YSBvbmx5LiBGaXJzdCBvZiBhbGwgd2UgbmVlZCB0byBjcmVhdGUgYSBkZXNpZ24gbWF0cml4LiBXZSBjYW4gZG8gdGhpcyBmcm9tIHRoZSBgcERhdGFgIHNpbmNlIHRoaXMgY29udGFpbnMgdGhlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBzYW1wbGVzCmBgYHtyfQpjb25kaXRpb24gPC0gcERhdGEodG90YWxfcHJvdGVpbl9xdWFudCkkQ29uZGl0aW9uCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofmNvbmRpdGlvbikKcHJpbnQoZGVzaWduKQpgYGAKClRoZW4gd2UgZml0IHRoZSBtb2RlbCB1c2luZyB0aGlzIGRlc2lnbiBhbmQgdXBkYXRlIHRoZSBlc3RpbWF0ZXMgZm9yIHRoZSBzdGFuZGFyZCBlcnJvcnMgZm9yIGVhY2ggY29lZmZpY2llbnQgdXNpbmcgdGhlIGBlQmF5ZXNgIGZ1bmN0aW9uLiBBcyBleHBlY3RlZCwgdGhlcmUgaXMgYSByZWxhdGlvbnNoaXAgYmV0d2VlbiBtZWFuIGludGVuc2l0eSBhbmQgdmFyaWFuY2UsIGFsdGhvdWdoIHRoaXMgaXMgYWxtb3N0IGFsbCBsaW1pdGVkIHRvIHRoZSB2ZXJ5IGxvdyBpbnRlbnNpdHkgdmFsdWVzLiAKClxjb2xvcmxldHtzaGFkZWNvbG9yfXt5ZWxsb3chMTB9IApcYmVnaW57c2hhZGVkfQpRdWVzdGlvbnM6Ci0gV2h5IGRvIHdlIGV4cGVjdCBhIHJlbGF0aW9uc2hpcCBiZXR3ZWVuIG1lYW4gaW50ZW5zaXR5IGFuZCB2YXJpYW5jZT8KXGVuZHtzaGFkZWR9Clxjb2xvcmxldHtzaGFkZWNvbG9yfXtsaWdodGdyYXkhMTB9IAoKTGltbWEgd2lsbCB1c2UgdGhpcyByZWxhdGlvbnNoaXAgdG8gbW9kZXJhdGUgdGhlIHN0YW5kYXJkIGVycm9ycyBmb3IgdGhlIGNvZWZmaWNpZW50cyBlc3RpbWF0ZWQgc3VjaCB0aGF0IHRoZSBwZXItcHJvdGVpbiB2YXJpYW5jZSBlc3RpbWF0ZXMgYXJlICJzcXVlZXplZCIgdG93YXJkcyB0aGUgZXhwZWN0YXRpb24gZGVyaXZlZCBmcm9tIG90aGVyIHByb3RlaW5zIHdpdGggc2ltaWxhciBtZWFuIGludGVuc2l0eS4KCmBgYHtyfQoKdGlkeSh0b3RhbF9wcm90ZWluX3F1YW50LCBhZGRQaGVubz1UUlVFKSAlPiUjICJ0aWR5IiB0aGUgb2JqZWN0LCBlLmcgbWFrZSBpdCBpbnRvIGEgdGlkeSBkYXRhIGZvcm1hdCAtLT4gbG9uZwogIGdyb3VwX2J5KHByb3RlaW4sIENvbmRpdGlvbikgJT4lICMgZ3JvdXAgYnkgcHJvdGVpbiBhbmQgY29uZGl0aW9uCiAgZHBseXI6OnN1bW1hcmlzZShtZWFuPW1lYW4odmFsdWUpLCBzcXJ0X3NkPXNxcnQoc2QodmFsdWUpKSkgJT4lICMgbWVhbiBhbmQgdmFyIGZvciBlYWNoIGdyb3VwCiAgZ2dwbG90KGFlcyhtZWFuLCBzcXJ0X3NkKSkgKyAjIHBsb3QgbWVhbihpbnRlbnNpdHkpIHZzIHNxcnQoc2QoaW50ZW5zaXR5KSkKICBnZW9tX3BvaW50KHNpemU9MC4xKSArCiAgZ2VvbV9zbW9vdGgoc2U9RkFMU0UsIG1ldGhvZD0ibG9lc3MiKSArICMgbG9jYWwgcmVncmVzc2lvbgogIHhsYWIoIk1lYW4gaW50ZW5zaXR5IikgKwogIHlsYWIoInNxcnQoc2QvbWVhbikiKQoKYGBgCgpCZWxvdyB3ZSBydW4gbGltbWEgdG8gaWRlbmlmeSB0aGUgcHJvdGVpbnMgd2l0aCBhIHNpZ25pZmljYW50IGNoYW5nZSBpbiBhYnVuZGFuY2UgYmV0d2VlbiBjb25kaXRpb25zCmBgYHtyfQp0b3RhbF9maXRfbG0gPC0gbG1GaXQoZXhwcnModG90YWxfcHJvdGVpbl9xdWFudCksIGRlc2lnbikgIyBmaXQgbW9kZWwgdG8gZWFjaCBwcm90ZWluCnRvdGFsX2ZpdF9sbV9jIDwtIGNvbnRyYXN0cy5maXQodG90YWxfZml0X2xtLCBjb2VmZmljaWVudHM9YygiY29uZGl0aW9uTSIpKSAjIGV4dHJhY3QgcmVzdWx0cyBmb3IgY29lZmZpY2llbnQgb2YgaW50ZXJlc3QKCnRvdGFsX2ZpdF9sbV9lX2MgPC0gZUJheWVzKHRvdGFsX2ZpdF9sbV9jKSAjIHNocmluayBzdGQgZXJyb3JzIHRvIGFidW5kYW5jZSB2cy4gc3RkZXYgdHJlbmQKCgoKcF92YWx1ZV9zdGF0dXMgPC0gaWZlbHNlKHRvdGFsX2ZpdF9sbV9lX2MkcC52YWx1ZVssJ2NvbmRpdGlvbk0nXTwwLjAxLCAic2lnIiwgIm5vdF9zaWciKSAjIGlkZW50aWZ5IHNpZ25pZmljYW50IGNoYW5nZXMKCiMgcGxvdApsaW1tYTo6cGxvdE1BKHRvdGFsX2ZpdF9sbV9lX2MsIHN0YXR1cz1wX3ZhbHVlX3N0YXR1cywKICAgICAgIGNvbD1jKGNiUGFsZXR0ZVs2XSwgImJsYWNrIiksIGNleD1jKDAuNSwwLjEpLCBtYWluPSIiKQoKCgpgYGAKTm90ZSB0aGF0IG1vc3Qgb2YgdGhlc2UgY2hhbmdlcyBhcmUgcmVsYXRpdmVseSBzbGlnaHQgKDwyLWZvbGQpCmBgYHtyfQojIEV4dHJhY3QgYWxsIHJlc3VsdHMgZnJvbSBsaW1tYSAobj1JbmYpCmFsbF9yZXN1bHRzIDwtIHRvcFRhYmxlKHRvdGFsX2ZpdF9sbV9lX2MsIGNvZWYgPSAiY29uZGl0aW9uTSIsIG4gPSBJbmYpCgpteV92b2xjYW5vcGxvdCA8LSBmdW5jdGlvbih0b3BUYWJsZVJlc3VsdHMpewogIHAgPC0gdG9wVGFibGVSZXN1bHRzICU+JQogICAgbXV0YXRlKHNpZz1pZmVsc2UoYWRqLlAuVmFsPDAuMDEsICJzaWcuIiwgIm5vdCBzaWcuIikpICU+JSAjIGFkZCAic2lnIiBjb2x1bW4KICAgIGdncGxvdChhZXMobG9nRkMsIC1sb2cxMChQLlZhbHVlKSwgY29sb3VyPXNpZykpICsKICAgIGdlb21fcG9pbnQoc2l6ZT0wLjI1KSArCiAgICBzY2FsZV9jb2xvdXJfbWFudWFsKHZhbHVlcz1jKCJibGFjayIsIGNiUGFsZXR0ZVs2XSksIG5hbWU9IiIpICMgbWFudWFsbHkgYWRqdXN0IGNvbG91cnMKICAKICByZXR1cm4ocCkKfQoKbXlfdm9sY2Fub3Bsb3QoYWxsX3Jlc3VsdHMpCmBgYAoKXGNvbG9ybGV0e3NoYWRlY29sb3J9e3llbGxvdyExMH0gClxiZWdpbntzaGFkZWR9ClF1ZXN0aW9uczoKCi0gV2hlcmUgYXJlIG1vc3Qgb2YgdGhlIGRhdGEgcG9pbnRzIGluIGEgdm9sY2FubyBwbG90PwoKLSBDYW4geW91IGVzdGltYXRlIHdoYXQgcHJvcG90aW9uIG9mIHByb3RlaW5zIGhhZCBhIHNpZ25pZmljYW50IGNoYW5nZSBpbiBhYnVuZGFuY2U/CgotIFdoeSBkb2VzIHRoZSBwbG90IGxvb2sgc28gc3ltZXRyaWNhbD8KXGVuZHtzaGFkZWR9Clxjb2xvcmxldHtzaGFkZWNvbG9yfXtsaWdodGdyYXkhMTB9IAoKT0ssIHNvIGl0J3MgZWFzeSB0byBwZXJmb3JtIHRoZSBwYWlyd2lzZSBjb21wYXJpc29uLiBXaGF0IGFib3V0IGNoYW5nZXMgaW4gUk5BIGJpbmRpbmc/IEZvciB0aGlzLCB3ZSBuZWVkIGNvbWJpbmUgdGhlIHR3byBNU25TZXRzIGludG8gYSBzaW5nbGUgRXhwcmVzc2lvblNldApgYGB7cn0KCmludGVyc2VjdGluZ19wcm90ZWlucyA8LSBpbnRlcnNlY3Qocm93bmFtZXModG90YWxfcHJvdGVpbl9xdWFudCksIHJvd25hbWVzKHJicF9wcm90ZWluX3F1YW50KSkKCnRvdGFsX2Zvcl9jb21iaW5hdGlvbiA8LSB0b3RhbF9wcm90ZWluX3F1YW50W2ludGVyc2VjdGluZ19wcm90ZWlucyxdCnJicF9mb3JfY29tYmluYXRpb24gPC0gcmJwX3Byb3RlaW5fcXVhbnRbaW50ZXJzZWN0aW5nX3Byb3RlaW5zLF0KCiMgbWFrZSB0aGUgY29sdW1uIG5hbWVzIGZvciB0aGUgdHdvIE1TblNldHMgdW5pcXVlCmNvbG5hbWVzKHRvdGFsX2Zvcl9jb21iaW5hdGlvbikgPC0gcGFzdGUwKGNvbG5hbWVzKHRvdGFsX2Zvcl9jb21iaW5hdGlvbiksICJfVG90YWwiKQpjb2xuYW1lcyhyYnBfZm9yX2NvbWJpbmF0aW9uKSA8LSBwYXN0ZTAoY29sbmFtZXMocmJwX2Zvcl9jb21iaW5hdGlvbiksICJfT09QUyIpCgojIG1ha2UgdGhlIEV4cHJlc3Npb25TZXQKY29tYmluZWRfaW50ZW5zaXRpZXMgPC0gRXhwcmVzc2lvblNldChjYmluZChleHBycyh0b3RhbF9mb3JfY29tYmluYXRpb24pLCBleHBycyhyYnBfZm9yX2NvbWJpbmF0aW9uKSkpCgojIEFkZCB0aGUgZmVhdHVyZSBkYXRhCmZEYXRhKGNvbWJpbmVkX2ludGVuc2l0aWVzKSA8LSBmRGF0YSh0b3RhbF9mb3JfY29tYmluYXRpb24pCgojIEFkZCB0aGUgcGhlbm90eXBlIGRhdGEKcERhdGEoY29tYmluZWRfaW50ZW5zaXRpZXMpIDwtIHJiaW5kKHBEYXRhKHRvdGFsX2Zvcl9jb21iaW5hdGlvbiksIHBEYXRhKHJicF9mb3JfY29tYmluYXRpb24pKQoKcERhdGEoY29tYmluZWRfaW50ZW5zaXRpZXMpJENvbmRpdGlvbiA8LSBmYWN0b3IocERhdGEoY29tYmluZWRfaW50ZW5zaXRpZXMpJENvbmRpdGlvbiwgbGV2ZWw9YygiTSIsIkcxIikpCnBEYXRhKGNvbWJpbmVkX2ludGVuc2l0aWVzKSRUeXBlIDwtIGZhY3RvcihwRGF0YShjb21iaW5lZF9pbnRlbnNpdGllcykkVHlwZSwgbGV2ZWw9YygiVG90YWwiLCJPT1BTIikpCgpkaW0oZXhwcnMoY29tYmluZWRfaW50ZW5zaXRpZXMpKQpwcmludChoZWFkKGRhdGEuZnJhbWUoZXhwcnMoY29tYmluZWRfaW50ZW5zaXRpZXMpKSwgMikpCnByaW50KGhlYWQoZkRhdGEoY29tYmluZWRfaW50ZW5zaXRpZXMpLCAyKSkKcHJpbnQoaGVhZChwRGF0YShjb21iaW5lZF9pbnRlbnNpdGllcyksIDIpKQpgYGAKCgpgYGB7cn0KY29uZGl0aW9uIDwtIGNvbWJpbmVkX2ludGVuc2l0aWVzJENvbmRpdGlvbgp0eXBlIDwtIGNvbWJpbmVkX2ludGVuc2l0aWVzJFR5cGUKc2FtcGxlX25hbWUgPC0gY29tYmluZWRfaW50ZW5zaXRpZXMkU2FtcGxlX25hbWUKCmRlc2lnbiA8LSBtb2RlbC5tYXRyaXgofmNvbmRpdGlvbip0eXBlKSAKCnJuYV9iaW5kaW5nX2ZpdCA8LSBsbUZpdChjb21iaW5lZF9pbnRlbnNpdGllcywgZGVzaWduKQoKcm5hX2JpbmRpbmdfZml0IDwtIGNvbnRyYXN0cy5maXQocm5hX2JpbmRpbmdfZml0LCBjb2VmZmljaWVudHM9ImNvbmRpdGlvbkcxOnR5cGVPT1BTIikKcm5hX2JpbmRpbmdfZml0IDwtIGVCYXllcyhybmFfYmluZGluZ19maXQpCgpybmFfYmluZGluZ19wX3ZhbHVlX3N0YXR1cyA8LSBpZmVsc2Uocm5hX2JpbmRpbmdfZml0JHAudmFsdWVbLCdjb25kaXRpb25HMTp0eXBlT09QUyddPDAuMDEsICJzaWciLCAibm90X3NpZyIpCgpsaW1tYTo6cGxvdE1BKHJuYV9iaW5kaW5nX2ZpdCwgc3RhdHVzPXJuYV9iaW5kaW5nX3BfdmFsdWVfc3RhdHVzLCB2YWx1ZXM9Yygic2lnIiwgIm5vdF9zaWciKSwKICAgICAgICAgICAgICBjb2w9YyhjYlBhbGV0dGVbNl0sICJibGFjayIpLCBjZXg9YygwLjUsMC4xKSwgbWFpbj0iIikKYGBgCkJlbG93LCB3ZSBzdW1tYXJpc2UgdGhlIG51bWJlciBvZiBzaWduZmljYW50IHAtdmFsdWVzIChwb3N0IEJIIEZEUiBjb3JyZWN0aW9uKSB1c2luZyBhIDElIEZEUiB0aHJlc2hvbGQuIApgYGB7cn0Kc3VtbWFyeShkZWNpZGVUZXN0cyhybmFfYmluZGluZ19maXQsIHAudmFsdWU9MC4wMSwgYWRqdXN0Lm1ldGhvZD0iQkgiKSkKYGBgCgpBbmQgdGhlbiBtYWtlIGFub3RoZXIgdm9sY2FubyBwbG90CmBgYHtyfQphbGxfcm5hX2JpbmRpbmdfcmVzdWx0cyA8LSB0b3BUYWJsZShybmFfYmluZGluZ19maXQsIGNvZWYgPSAiY29uZGl0aW9uRzE6dHlwZU9PUFMiLCBuID0gSW5mLCBjb25maW50PVRSVUUpCgpteV92b2xjYW5vcGxvdChhbGxfcm5hX2JpbmRpbmdfcmVzdWx0cykKYGBgClNvIGFnYWluLCBsb3RzIG9mIFJOQSBiaW5kaW5nIGNoYW5nZXMhCgpOb3csIGxldCdzIGNvbXBhcmUgdGhlIHJlc3VsdHMgZnJvbSB0aGUgdHdvIG1ldGhvZHMuIFRvIGRvIHRoaXMsIHdlIHdpbGwgbWVyZ2UgdG9nZXRoZXIgdGhlIHJlc3VsdHMgZnJvbSB0aGUgdHdvIG1ldGhvZHMuCmBgYHtyfQpNX0cxX3NpbXBsZV9sbSA8LSByZWFkUkRTKCIuLi9yZXN1bHRzL01fRzFfY2hhbmdlc19pbl9STkFfYmluZGluZ19saW5lYXJfbW9kZWwucmRzIikKCmNvbXBhcmVfbWV0aG9kcyA8LSBhbGxfcm5hX2JpbmRpbmdfcmVzdWx0cyAlPiUKICBkcGx5cjo6c2VsZWN0KCJsb2dGQyIsICJBdmVFeHByIiwgImFkai5QLlZhbCIsICJQLlZhbHVlIikgJT4lCiAgbWVyZ2UoTV9HMV9zaW1wbGVfbG0sIGJ5Lng9InJvdy5uYW1lcyIsIGJ5Lnk9InByb3RlaW4iKQpgYGAKCkZpcnN0LCBsZXQncyB0YWJ1bGF0ZSB0aGUgcHJvdGVpbnMgc2lnbmlmaWNhbnQgaW4gZWFjaCBtZXRob2QKYGBge3J9CmxtX3NpZyA8LSBpZmVsc2UoY29tcGFyZV9tZXRob2RzJGxtX0JIPDAuMDEsICJsbSBzaWciLCAibG0gbm90IHNpZyIpCmxpbW1hX3NpZyA8LSBpZmVsc2UoY29tcGFyZV9tZXRob2RzJGFkai5QLlZhbDwwLjAxLCAibGltbWEgc2lnIiwgImxpbW1hIG5vdCBzaWciKQoKcHJpbnQodGFibGUobG1fc2lnLCBsaW1tYV9zaWcpKQoKY29tcGFyZV9tZXRob2RzJHNpZ19zdGF0dXMgPC0gaW50ZXJhY3Rpb24obG1fc2lnLCBsaW1tYV9zaWcpCmBgYAoKT0ssIHNvIG1vc3QgcHJvdGVpbnMgd2l0aCBzaWduaWZpY2FudCBjaGFuZ2UgaW4gUk5BIGJpbmRpbmcgdXNpbmcgYGxtYCBvciBgbGltbWFgIGFyZSBzaWduaWZpY2FudCBpbiBib3RoLCBhbHRob3VnaCBgbGltbWFgIGRvZXMgaW5kaWNhdGUgbW9yZSBwcm90ZWlucyBoYXZlIGEgc2lnbmlmaWNhbnQgY2hhbmdlLiBOb3RlIHRoYXQgb25seSAyNS8xOTE2IHByb3RlaW5zIGFyZSBzaWduaWZpY2FudCBieSBgbG1gIG9ubHkuCgpXaGF0IGFib3V0IGlmIHdlIHNlcGFyYXRlIGJ5IHRoZSBgbG1gIG1vZGVsIHVzZWQsIGUuZyArLy0gdGFnCmBgYHtyfQpmaXQgPC0gY29tcGFyZV9tZXRob2RzJGZpdApwcmludCh0YWJsZShsbV9zaWcsIGxpbW1hX3NpZywgZml0KSkKCmBgYApPSywgc28gYGxtYCBhbmQgYGxpbW1hYCByZXN1bHRzIGFyZSBtb3JlIHNpbWlsYXIgaWYgdGhlIGBsbWAgbW9kZWwgZGlkIG5vdCBpbmNsdWRlIHRoZSB0YWcuIFRoaXMgaXMgbm8gc3VwcmlzZSBzaW5jZSB0aGUgYGxpbW1hYCBmb211bGEgd2UgdXNlZCBkaWQgbm90IGluY2x1ZGUgdGhlIHRhZyBjb3ZhcmlhdGUgc28gdGhpcyBpcyB0aGUgY2xvc2VzdCBjb21wYXJpc29uCgpGaXJzdCwgbGV0J3MgY29tcGFyZSB0aGUgcC12YWx1ZXMuIE5vdGUgdGhhdCB0aGUgcC12YWx1ZXMgYXJlIHVzdWFsbHkgbG93ZXIgaW4gYGxpbW1hYC4gVGhlIHNlY29uZCBwbG90IHNob3dzIHRoZSB0aHJlc2hvbGQgZm9yIHRoZSBtYXhpbXVtIHAtdmFsdWUgd2hpY2ggcmVzdWx0cyBpbiBhbiBlc3RpbWF0ZWQgRkRSIDwgMSUgZm9yIGJvdGggbWV0aG9kcy4gCmBgYHtyfQptYXhfcF9zaWdfbG0gPC0gY29tcGFyZV9tZXRob2RzICU+JSBmaWx0ZXIobG1fQkg8MC4wMSkgJT4lIHB1bGwobG1fcF92YWx1ZSkgJT4lIG1heCgpCm1heF9wX3NpZ19saW1tYSA8LSBjb21wYXJlX21ldGhvZHMgJT4lIGZpbHRlcihhZGouUC5WYWw8MC4wMSkgJT4lIHB1bGwoUC5WYWx1ZSkgJT4lIG1heCgpCgpwIDwtIGNvbXBhcmVfbWV0aG9kcyAlPiUKICBnZ3Bsb3QoYWVzKGxvZzEwKGxtX3BfdmFsdWUpLCBsb2cxMChQLlZhbHVlKSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKHNsb3BlPTEsIGxpbmV0eXBlPTIsIGNvbG91cj1jYlBhbGV0dGVbNl0pICsKICB4bGFiKCJsbSIpICsKICB5bGFiKCJsaW1tYSIpIAoKcHJpbnQocCkKCnByaW50KHAgKwogIGdlb21fdmxpbmUoeGludGVyY2VwdD1sb2cxMChtYXhfcF9zaWdfbG0pLCBsaW5ldHlwZT0yKSArCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0PWxvZzEwKG1heF9wX3NpZ19saW1tYSksIGxpbmV0eXBlPTIpKQpgYGAKClxjb2xvcmxldHtzaGFkZWNvbG9yfXtibHVlITEwfSAKXGJlZ2lue3NoYWRlZH0KVGFzazogQ29weSB0aGUgY2VsbCBhYm92ZSBpbnRvIGEgbmV3IGNlbGwgYW5kIG1vZGlmeSB0aGUgcGxvdHRpbmcgY29kZSB0byBpbmRpY2F0ZSB3aGljaCBsbSBtb2RlbCB3YXMgdXNlZApcZW5ke3NoYWRlZH0KXGNvbG9ybGV0e3NoYWRlY29sb3J9e2xpZ2h0Z3JheSExMH0gCgpXaGF0IGFib3V0IGlmIHdlIHNlcGFyYXRlIHRoZSBwcm90ZWlucyBieSB0aGVpciBpbnRlbnNpdHk/CmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03fQoKY29tcGFyZV9tZXRob2RzICU+JQogIG11dGF0ZShiaW5uZWRfYXZlX2V4cHJzPWN1dDIoQXZlRXhwciwgZz0xMCkpICU+JSAjIGJpbiB0aGUgQXZlRXhwciB1c2luZyBIbWlzYzo6Y3V0KCkKICBnZ3Bsb3QoYWVzKGxvZzEwKGxtX3BfdmFsdWUpLCBsb2cxMChQLlZhbHVlKSkpICsKICBnZW9tX3BvaW50KCkgKwogIGdlb21fYWJsaW5lKHNsb3BlPTEsIGxpbmV0eXBlPTIpICsKICB4bGFiKCJsbSIpICsKICB5bGFiKCJsaW1tYSIpICsKICBmYWNldF93cmFwKH5iaW5uZWRfYXZlX2V4cHJzKQoKYGBgClxjb2xvcmxldHtzaGFkZWNvbG9yfXt5ZWxsb3chMTB9IApcYmVnaW57c2hhZGVkfQpRdWVzdGlvbjogV2h5IGFyZSB0aGUgcC12YWx1ZXMgbW9yZSBzaW1pbGFyIGZvciBsb3cgaW50ZW5zaXR5IHByb3RlaW5zPwpcZW5ke3NoYWRlZH0KXGNvbG9ybGV0e3NoYWRlY29sb3J9e2xpZ2h0Z3JheSExMH0gCgpOb3RlIHRoYXQgdGhlIGVzdGltYXRlcyBmb3IgdGhlIGZvbGQgY2hhbmdlIGFyZSBub3QgY2hhbmdlZCBieSB0aGUgYmF5ZXNpYW4gc2hyaW5rYWdlIG9mIHRoZSBjb2VmZmljaWVudCBzdGFuZGFyZCBlcnJvcnMuCmBgYHtyfQpjb21wYXJlX21ldGhvZHMgJT4lIGdncGxvdChhZXMobG9nMTAobG1fZm9sZF9jaGFuZ2UpLCBsb2cxMChsb2dGQykpKSArCiAgZ2VvbV9wb2ludChzaXplPTEsIGFscGhhPTAuMikgKwogIHhsYWIoImxtIikgKwogIHlsYWIoImxpbW1hIikgKwogIGdndGl0bGUoIkVzdGltYXRlZCBmb2xkIGNoYW5nZXMiKQpgYGAKCkZpbmFsbHksIGxldCdzIGV4cGxvcmUgc29tZSBvZiB0aGUgcHJvdGVpbnMgd2hpY2ggd2VyZSBkZXRlY3RlZCBhcyBoYXZpbmcgYSBzaWduaWZpY2FudCBjaGFuZ2UgaW4gUk5BIGJpbmRpbmcgd2l0aCBvbmx5IG9uZSBtZXRob2QuIFJlbWVtYmVyIGZyb20gYWJvdmUgdGhhdCB0aGUgcC12YWx1ZXMgZm9yIGxtIGFuZCBsaW1tYSBhcmUgdmVyeSB3ZWxsIGNvcnJlbGF0ZWQgc28gd2UncmUgbG9va2luZyBoZXJlIGF0IHNsaWdodCBkaWZmZXJlbmNlcyBjbG9zZSB0byB0aGUgMSUgRkRSIHRocmVzaG9sZHMuIAoKRmlyc3QsIHdlIG5lZWQgYSBmdW5jdGlvbiB0byBwbG90IHRoZSBpbnRlbnNpdGllcyBmb3IgYSBwcm90ZWluKHMpCmBgYHtyfQojIEZ1bmN0aW9uIHRvIHBsb3QgdGhlIGludGVuc2l0aWVzIHZhbHVlcwpwbG90SW50ZW5zaXRpZXMgPC0gZnVuY3Rpb24ob2JqKXsKICAKICBwIDwtIHRpZHkob2JqLCBhZGRQaGVubz1UUlVFKSAlPiUKICAgIGdncGxvdChhZXMoQ29uZGl0aW9uLCB2YWx1ZSwgY29sb3VyPVR5cGUsIGdyb3VwPVR5cGUpKSArCiAgICBnZW9tX3BvaW50KCkgKwogICAgc3RhdF9zdW1tYXJ5KGdlb209ImxpbmUiLCBmdW4ueT1tZWFuKSArCiAgICBmYWNldF93cmFwKH5nZW5lLCBzY2FsZXM9J2ZyZWUnKSArCiAgICB5bGFiKCJJbnRlbnNpdHkgKGxvZzIpIikKICAgIAogIGludmlzaWJsZShwKQp9CgpgYGAKCkxldCdzIGxvb2sgYXQgdGhlIHByb3RlaW5zIHdoaWNoIGFyZSAibG0gb25seSIuIFdlJ2xsIGlnbm9yZSB0aG9zZSB3aGVyZSB3ZSB1c2VkIHRoZSBUTVQgdGFnIGluIG91ciBsbSBtb2RlbCBzaW5jZSB0aGlzIHdhcyBub3QgaW5jbHVkZWQgaW4gdGhlIGxpbW1hIG1vZGVsIHNvIHRoYXQgbWF5IGJlIGFub3RoZXIgcmVhc29uIGZvciBkaWZmZXJlbmNlcyBpbiB0aGUgcC12YWx1ZXMuCgpgYGB7cn0KbG1fb25seSA8LSBjb21wYXJlX21ldGhvZHMgJT4lCiAgZmlsdGVyKGZpdD09IldpdGhvdXRfdGFnIikgJT4lICMgUmVzdHJpY3QgdG8gcHJvdGVpbiB3aGVyZSBsbSBtb2RlbCBkaWQgbm90IGluY2x1ZGUgdGhlIHRhZwogIGZpbHRlcihzaWdfc3RhdHVzPT0nbG0gc2lnLmxpbW1hIG5vdCBzaWcnKSAlPiUgIyBzaWcgaW4gbG0gb25seQogIGRwbHlyOjpzZWxlY3QoUm93Lm5hbWVzLCBsbV9mb2xkX2NoYW5nZSwgUC5WYWx1ZSwgYWRqLlAuVmFsLCBsbV9wX3ZhbHVlLCBsbV9CSCwgbG1fc3RkX2Vycm9yKSAlPiUgIyBzZWxlY3QgY29sdW1ucwogIGFycmFuZ2UoZGVzYyhQLlZhbHVlKSkgIyBhcnJhbmdlIGJ5IGxpbW1hIHAtdmFsdWUgKGRlc2NlbmRpbmcgb3JkZXIpCgpwcmludChsbV9vbmx5KQpgYGAKCk5vdyBsZXQncyBwbG90IHRoZXNlIHByb3RlaW5zLiBOb3RpY2UgdGhhdCBpbiBhbGwgY2FzZXMsIHRoZSByZXBsaWNhdGVzIGFyZSB2ZXJ5IHRpZ2h0bHkgZGlzdHJpYnV0ZWQuCmBgYHtyLCBmaWcuaGVpZ2h0PTcsIGZpZy53aWR0aD03fQpwcmludChwbG90SW50ZW5zaXRpZXMoY29tYmluZWRfaW50ZW5zaXRpZXNbbG1fb25seSRSb3cubmFtZXMsXSkpCmBgYApJbiBzb21lIGNhc2VzLCB0aGUgaW50ZW5zaXR5IHZhbHVlcyBhcmUgbmVhciBpZGVudGljYWwgKHNlZSBiZWxvdykuCgpgYGB7cn0KIyBIZXRlcm9nZW5lb3VzIG51Y2xlYXIgcmlib251Y2xlb3Byb3RlaW4gVS1saWtlIHByb3RlaW4gMQojIEhOUk5QVUwxCiMgQWN0cyBhcyBhIGJhc2ljIHRyYW5zY3JpcHRpb25hbCByZWd1bGF0b3IuIFJlcHJlc3NlcyBiYXNpYyB0cmFuc2NyaXB0aW9uIGRyaXZlbiBieSBzZXZlcmFsIHZpcnVzIGFuZCBjZWxsdWxhciBwcm9tb3RlcnMuCiMgV2hlbiBhc3NvY2lhdGVkIHdpdGggQlJENywgYWN0aXZhdGVzIHRyYW5zY3JpcHRpb24gb2YgZ2x1Y29jb3J0aWNvaWQtcmVzcG9uc2l2ZSBwcm9tb3RlciBpbiB0aGUgYWJzZW5jZSBvZgojIGxpZ2FuZC1zdGltdWxhdGlvbi4gUGxheXMgYWxzbyBhIHJvbGUgaW4gbVJOQSBwcm9jZXNzaW5nIGFuZCB0cmFuc3BvcnQuIEJpbmRzIGF2aWRseSB0byBwb2x5KEcpIGFuZCBwb2x5KEMpIFJOQQojIGhvbW9wb2x5bWVycyBpbiB2aXRyby4KCnRpZHkoY29tYmluZWRfaW50ZW5zaXRpZXMsIGFkZFBoZW5vPVRSVUUpICU+JQogIGZpbHRlcihnZW5lPT0iUTlCVUoyIiwgVHlwZT09Ik9PUFMiLCBDb25kaXRpb249PSJNIikgJT4lCiAgZHBseXI6OnNlbGVjdChDb25kaXRpb24sIFJlcGxpY2F0ZSwgdmFsdWUpCmBgYApcY29sb3JsZXR7c2hhZGVjb2xvcn17eWVsbG93ITEwfSAKXGJlZ2lue3NoYWRlZH0KUXVlc3Rpb25zOgoKLSBXaHkgd291bGQgdGhlIGludGVuc2l0eSB2YWx1ZXMgZm9yIHRoaXMgcHJvdGVpbiBiZSBzbyBzaW1pbGFyPwpcZW5ke3NoYWRlZH0KCgoKClxjb2xvcmxldHtzaGFkZWNvbG9yfXtncmVlbiExMH0gClxiZWdpbntzaGFkZWR9Ck15IGFuc3dlcjogV2hpbGUgaXQncyBwb3NzaWJsZSB0aGUgYmlvbG9naWNhbCB2YXJpYWJpbGl0eSBmb3IgdGhpcyBwcm90ZWluIGlzIHZlcnkgbG93LCBpdCBzZWVtcyB1bmxpa2VseSB0aGF0IHRoZSBleGFjdCBzYW1lIGFtb3VudCBvZiBSTkEtYm91bmQgcHJvdGVpbiB3YXMgcmVjb3ZlcmVkIGdpdmVuIHRoZSBleHBlY3RlZCB0ZWNobmljYWwgdmFyaWFiaWxpdHkgZnJvbSB0aGUgT09QUyBwcm90b2NvbCBhbmQgc2FtcGxlIHByZXBhcmF0aW9uLiBBIG11Y2ggbW9yZSBsaWtlbHkgZXhwbGFuYXRpb24gaXMgdGhhdCB0aGVzZSBpbnRlbnNpdHkgdmFsdWVzIGFyZSBzbyBzaW1pbGFyIHNpbXBseSBieSBjaGFuY2UuIFRoaXMgaXMgdGhlIChyZWFzb25hYmxlKSBhc3N1bXB0aW9uIGJ5IHdoaWNoIGBsaW1tYWAgYWx0ZXJzIHRoZSBzdGFuZGFyZCBkZXZpYXRpb25zIGZvciB0aGUgY29lZmZpY2llbnRzIHVzaW5nIGZlYXR1cmVzIHdpdGggc2ltaWxhciBhYnVuZGFuY2UuIApcZW5ke3NoYWRlZH0gCiBcY29sb3JsZXR7c2hhZGVjb2xvcn17bGlnaHRncmF5ITEwfSAKIApCZWxvdywgd2UgZXhwbG9yZSB0aGUgb2JzZXJ2ZWQgaW50ZW5zaXR5IHZhbHVlcyBmb3Igc29tZSBvZiB0aGUgcHJvdGVpbnMgd2hpY2ggYXJlIHNpZ25pZmljYW50IGFjY29yZGluZyBvbmx5IHRvIGBsaW1tYWAuIE5vdGUgdGhhdCB0aGVzZSBoYXZlIHJlbGF0aXZlbHkgbGFyZ2UgdmFyaWFiaWxpdHkgaW4gY29tcGFyaXNvbi4KYGBge3IsIGZpZy5oZWlnaHQ9NywgZmlnLndpZHRoPTd9CmxpbW1hX29ubHkgPC0gY29tcGFyZV9tZXRob2RzICU+JQogIGZpbHRlcihzaWdfc3RhdHVzPT0nbG0gbm90IHNpZy5saW1tYSBzaWcnLCBmaXQ9PSJXaXRob3V0X3RhZyIpICU+JQogIGRwbHlyOjpzZWxlY3QoUm93Lm5hbWVzLCBsbV9mb2xkX2NoYW5nZSwgUC5WYWx1ZSwgYWRqLlAuVmFsLCBsbV9wX3ZhbHVlLCBsbV9CSCwgbG1fc3RkX2Vycm9yKSAlPiUKICBhcnJhbmdlKGRlc2MobG1fcF92YWx1ZSkpCgpwcmludChoZWFkKGxpbW1hX29ubHkpKQoKcHJpbnQocGxvdEludGVuc2l0aWVzKGNvbWJpbmVkX2ludGVuc2l0aWVzW2xpbW1hX29ubHkkUm93Lm5hbWVzWzE6OV0sXSkpCmBgYAoKTGV0J3MgZ28gYmFjayB0byB0aGF0IHBsb3Qgb2YgbWVhbiB2cyBzcXJ0IGFuZCBzZWUgaG93IHRoZSBwcm90ZWlucyBjb21wYXJlIGRlcGVuZGluZyBvbiB3aGV0aGVyIHRoZXkgd2VyZSBkZXRlY3RlZCBhcyBzaWduZmljYW50IGluIGVhY2ggbWV0aG9kLgoKQXMgZXhwZWN0ZWQsIHRob3NlIHNpZ25pZmljYW50IGluIGp1c3QgYGxtYCBoYXZlIHJlbGF0aXZlbHkgbG93IG9ic2VydmVkIHN0ZC4gZGV2LiBhbmQgdGhvc2Ugc2lnbmlmaWNhbnQgaW4ganVzdCBgbGltbWFgIGhhdmUgcmVsYXRpdmVseSBoaWdoIHN0ZC4gZGV2LgpgYGB7cn0KbWVhbl9zZF9kYXRhIDwtIHRpZHkodG90YWxfcHJvdGVpbl9xdWFudCwgYWRkUGhlbm89VFJVRSkgJT4lIyAidGlkeSIgdGhlIG9iamVjdCwgZS5nIG1ha2UgaXQgaW50byBhIHRpZHkgZGF0YSBmb3JtYXQgLS0+IGxvbmcKICBncm91cF9ieShwcm90ZWluLCBDb25kaXRpb24pICU+JSAjIGdyb3VwIGJ5IHByb3RlaW4gYW5kIGNvbmRpdGlvbgogIGRwbHlyOjpzdW1tYXJpc2UobWVhbj1tZWFuKHZhbHVlKSwgc3FydF9zZD1zcXJ0KHNkKHZhbHVlKSkpICU+JSAjIG1lYW4gYW5kIHN0ZGV2IGZvciBlYWNoIGdyb3VwCiAgdW5ncm91cCgpICU+JQogIG1lcmdlKGNvbXBhcmVfbWV0aG9kcywgYnkueD0icHJvdGVpbiIsIGJ5Lnk9IlJvdy5uYW1lcyIpICU+JSAjIG1lcmdlIGluIHRoZSByZXN1bHRzIGZyb20gdGhlIHR3byBtZXRob2RzCiAgZmlsdGVyKGZpdD09IldpdGhvdXRfdGFnIikgIyBPbmx5IGtlZXAgdGhvc2UgcHJvdGVpbnMgZml0dGVkIHdpdGhvdXQgdGhlIHRhZyBjb3ZhcmlhdGUgaW4gbG0KCiMgcmVtYWtlIHRoZSBiYXNpYyBwbG90IHNob3dpbmcgdGhlIHJlbGF0aW9uc2hpcApwX2Jhc2ljIDwtIG1lYW5fc2RfZGF0YSAlPiUKICBnZ3Bsb3QoYWVzKG1lYW4sIHNxcnRfc2QpKSArIAogIHhsYWIoIk1lYW4gaW50ZW5zaXR5IikgKwogIHlsYWIoIkNvZWZmaWNpZW50IG9mIHZhcmlhbmNlXG4oc2QvbWVhbikiKSArCiAgeGxpbSgwLCBOQSkgKyB5bGltKDAsIE5BKSAjIGluY2x1ZGUgMCwwCgpwcmludChwX2Jhc2ljICsgZ2VvbV9wb2ludChzaXplPTAuMiwgYWxwaGE9MC41KSArIGdlb21fc21vb3RoKHNlPUZBTFNFKSkKCnBfZGVuc2l0eSA8LSBwX2Jhc2ljICsKICBnZW9tX2RlbnNpdHlfMmQoYWVzKGNvbG91cj1zaWdfc3RhdHVzKSwgYWxwaGE9MC41LCBzaXplPTAuNSkgKyAjIGRlbnNpdHkgb2YgcG9pbnRzCiAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXM9Y2JQYWxldHRlW2MoMjo0LDYpXSkjIHNldCBjb2xvdXJzCgpwcmludChwX2RlbnNpdHkpCgpgYGAKCkZpbmFsbHksIHdlIGNhbiBnZXQgYGxpbW1hYCB0byByZXR1cm4gdGhlIHNpZ25maWNhbnQgY2hhbmdlcyB1c2luZyBib3RoIHAtdmFsdWUgYW5kIGxvZy1mb2xkIGNoYW5nZSB0aHJlc2hvbGRzIHVzaW5nIHRoZSBUUkVBVCBtZXRob2QgKGh0dHBzOi8vd3d3Lm5jYmkubmxtLm5paC5nb3YvcHVibWVkLzE5MTc2NTUzKS4gTm90ZSB0aGF0IHRoaXMgaXMgbm90IHRoZSBzYW1lIGFzIHRocmVzaG9sZGluZyBvbiB0aGUgcC12YWx1ZSBhbmQgdGhlIHBvaW50IGVzdGltYXRlIGZvciB0aGUgZm9sZCBjaGFuZ2UgYXMgbGltbWEgaXMgYWN0dWFsbHkgdGVzdGluZyB0aGUgbnVsbCBoeXBvdGhlc2lzIHRoYXQgdGhlIGZvbGQgY2hhbmdlIGlzIGxlc3MgdGhhbiBvdXIgc3BlY2lmaWVkIHRocmVzaG9sZC4gVG8gYmUgZXhwbGljaXQsIGxldCdzIGNoZWNrIHRoZSBkaWZmZXJlbmNlCmBgYHtyfQpzaWdfY2hhbmdlc19wIDwtIHRvcFRhYmxlKHJuYV9iaW5kaW5nX2ZpdCwgY29lZiA9ICJjb25kaXRpb25HMTp0eXBlT09QUyIsIG4gPSBJbmYsIHAudmFsdWU9MC4wMSwgYWRqdXN0Lm1ldGhvZD0iZmRyIiwgY29uZmludD0wLjk1KQpzaWdfY2hhbmdlc19wX2xvZ2ZjX3BvaW50X2VzdGltYXRlIDwtIHNpZ19jaGFuZ2VzX3BbYWJzKHNpZ19jaGFuZ2VzX3AkbG9nRkMpPjEsXQpjYXQoc3ByaW50ZigiJXMgcHJvdGVpbnMgcGFzcyBhZGp1c3RlZCBwLXZhbHVlIHRocmVzaG9sZCwgb2Ygd2hpY2ggJXMgcGFzcyB0aGUgdGhyZXNob2xkIG9uIDIgZm9sZCBjaGFuZ2UgcG9pbnQgZXN0aW1hdGVcbiIsIAogICAgICAgICAgICBucm93KHNpZ19jaGFuZ2VzX3ApLCBucm93KHNpZ19jaGFuZ2VzX3BfbG9nZmNfcG9pbnRfZXN0aW1hdGUpKSkKCgpybmFfYmluZGluZ19maXRfdHJlYXQgPC0gdHJlYXQocm5hX2JpbmRpbmdfZml0LGxmYz0xKSAjIFRlc3QgbnVsbCBoeXBvdGhlc2lzIHRoYW4gY2hhbmdlIGlzIDwyLWZvbGQgCnNpZ19jaGFuZ2VzX3BfbG9nZmMgPC0gdG9wVHJlYXQocm5hX2JpbmRpbmdfZml0X3RyZWF0LCBjb2VmID0gImNvbmRpdGlvbkcxOnR5cGVPT1BTIiwgbiA9IEluZiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBwLnZhbHVlPTAuMDEsIGxmYz0xLCBhZGp1c3QubWV0aG9kPSJmZHIiLCBjb25maW50PTAuOTUpCmNhdChzcHJpbnRmKCIlcyBwcm90ZWlucyBwYXNzIHRoZSBjb21iaW5lZCBhZGp1c3RlZCBwLXZhbHVlIHRocmVzaG9sZCArIGZvbGQgY2hhbmdlID4gMlxuIiwgIG5yb3coc2lnX2NoYW5nZXNfcF9sb2dmYykpKQpgYGAKCkFuZCBiZWxvdyB3ZSByZXByb2R1Y2Ugb3VyIHZvbGNhbm8gcGxvdCBpbmNsdWRpbmcgdGhlIDk1JSBjb25maWRlbmNlIGludGVydmFsIGFuZCBoaWdobGlnaHQgdGhvc2UgcHJvdGVpbnMgd2hpY2ggaGF2ZSA8IDElIEZEUiBhbmQgYW4gYWJzb2x1dGUgZm9sZCBjaGFuZ2Ugc2lnbmlmaWNhbnRseSBncmVhdGVyIHRoYW4gMi4gQmVsb3csIHdlIGNhbiBzZWUgdGhhdCBtYW55IG9mIHRoZSBwcm90ZWlucyB3aXRoIGEgZm9sZCBjaGFuZ2UgKEZDKSBwb2ludCBlc3RpbWF0ZSA+IDIgaGF2ZSBhIDk1JSBjb25maWRlbmNlIGludGVydmFsIHRoYXQgb3ZlcmxhcHMgdGhlIGRhc2hlZCBsaW5lcyBmb3IgPjItZm9sZCBjaGFuZ2UuIFRSRUFUIGFsc28gdGFrZXMgdGhlIG11bHRpcGxlIHRlc3RpbmcgaW50byBhY2NvdW50IHNvIGl0J3MgZXZlbiBtb3JlIGNvbnNlcnZhdGl2ZSB0aGFuIGp1c3QgdXNpbmcgdGhlIDk1JSBDSSBzaG93biBiZWxvdy4KCk9mIGNvdXJzZSwgdGhlIHRocmVzaG9sZCBmb3IgdGhlIGZvbGQgY2hhbmdlcyB5b3UgYXJlIGludGVyZXN0ZWQgaW4gZGVwZW5kcyBlbnRpcmVseSBvbiB5b3VyIHByaW9yIGV4cGVjdGF0aW9ucy4KYGBge3IsIGZpZy5oZWlnaHQ9NiwgZmlkLndpZHRoPTZ9Ci50bXBfZGYgPC0gYWxsX3JuYV9iaW5kaW5nX3Jlc3VsdHMKLnRtcF9kZiRzaWcgPC0gaWZlbHNlKC50bXBfZGYkUC5WYWx1ZTw9MC4wMSwgIjwxJSBGRFIiLCAiPjElIEZEUiIpICMgYWRkICJzaWciIGNvbHVtbgoudG1wX2RmJHNpZ1tyb3duYW1lcygudG1wX2RmKSAlaW4lIHJvd25hbWVzKHNpZ19jaGFuZ2VzX3BfbG9nZmNfcG9pbnRfZXN0aW1hdGUpXSA8LSAiPDElIEZEUi4gRkMgcG9pbnQgZXN0aW1hdGUgPCAyIgoudG1wX2RmJHNpZ1tyb3duYW1lcygudG1wX2RmKSAlaW4lIHJvd25hbWVzKHNpZ19jaGFuZ2VzX3BfbG9nZmMpXSA8LSAiPDElIEZEUi4gVFJFQVQgRkMgPCAyIgoudG1wX2RmJFNFIDwtIHNxcnQocm5hX2JpbmRpbmdfZml0JHMyLnBvc3QpICogcm5hX2JpbmRpbmdfZml0JHN0ZGV2LnVuc2NhbGVkWywxXQoKcCA8LSAudG1wX2RmICU+JQogIGdncGxvdChhZXMobG9nRkMsIC1sb2cxMChQLlZhbHVlKSwgY29sb3VyPXNpZykpICsKICBnZW9tX3BvaW50KHNpemU9MSkgKwogIHNjYWxlX2NvbG91cl9tYW51YWwodmFsdWVzPWMoY2JQYWxldHRlW2MoNiwyLDcpXSwgImdyZXkyMCIpLCBuYW1lPSIiKSArICMgbWFudWFsbHkgYWRqdXN0IGNvbG91cnMKICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQ9MSwgbGluZXR5cGU9MiwgY29sb3VyPSJncmV5NzAiKSArCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PS0xLCBsaW5ldHlwZT0yLCBjb2xvdXI9ImdyZXk3MCIpICsKICB0aGVtZShsZWdlbmQucG9zaXRpb249InRvcCIsIGxlZ2VuZC5kaXJlY3Rpb249MikKICAKcHJpbnQocCkKCnByaW50KHAgKyBnZW9tX2Vycm9yYmFyaChhZXMoeG1pbj1DSS5MLCB4bWF4PUNJLlIpKSkKYGBgCgoKCkZpbmFsbHksIGxldCdzIHNhdmUgb3V0IHRoZSByZXN1bHRzIG9iamVjdHMgZm9yIGxhdGVyIG5vdGVib29rcy4KYGBge3J9CnNhdmVSRFMocm5hX2JpbmRpbmdfZml0LCAiLi4vcmVzdWx0cy9saW1tYV9ybmFfYmluZGluZ19maXQucmRzIikKCnNhdmVSRFMoYWxsX3JuYV9iaW5kaW5nX3Jlc3VsdHMsICIuLi9yZXN1bHRzL2xpbW1hX3JuYV9iaW5kaW5nX3Jlc3VsdHMucmRzIikKCnNhdmVSRFMocm5hX2JpbmRpbmdfZml0X3RyZWF0LCAiLi4vcmVzdWx0cy9saW1tYV9ybmFfYmluZGluZ19yZXN1bHRzX3RyZWF0LnJkcyIpCgpzYXZlUkRTKGNvbXBhcmVfbWV0aG9kcywgIi4uL3Jlc3VsdHMvY29tcGFyZV9tZXRob2RzX3JuYV9iaW5kaW5nX3Jlc3VsdHMucmRzIikKCnNhdmVSRFMoY29tYmluZWRfaW50ZW5zaXRpZXMsICIuLi9yZXN1bHRzL2NvbWJpbmVkX2ludGVuc2l0aWVzLnJkcyIpCgpgYGAKCgoK